package com.lindenliu.crypto.hash
{
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	
	public class Tiger
	{
		include "data/sboxes.as";
		public function Tiger()
		{
		}
		public static function tiger(str:String):Array
		{
			var res:Array = [	{op0:0x89ABCDEF, op1:0x01234567}, 
								{op0:0x76543210, op1:0xFEDCBA98},
								{op0:0xC3B2E187, op1:0xF096A5B4}
			];
			var data:ByteArray = new ByteArray();
			data.writeUTFBytes(str);
			data.position = 0;
			var dataLength:uint = data.bytesAvailable;
			var partition:Array = [];
			var i:int = 0;
			var j:int = 0;
			while ( data.bytesAvailable >= 64 )
			{
				partition = [];
				for (j=0; j < 64; ++j)
				{
					partition[j]=data.readUnsignedByte();
				}
				tigerCompress(partition, res); 
			}
			
			
			partition = new Array(64);
			i = data.bytesAvailable;
			
			for (j=0;j<i; ++j)
			{
				partition[j]=data.readUnsignedByte();
			}
			partition[j] = 0x01;
			
			++j;
			for(; j&7; ++j)
			{
				partition[j] = 0;
			}
			if ( j > 56 )
			{
				for(; j < 64; ++j)
				{
					partition[j]=0;
				}
				tigerCompress(partition, res);
				j = 0;
			}
			
			for ( ; j < 56; ++j)
				partition[j] = 0;
			dataLength = dataLength << 3;
			for ( i=0; i < 4; ++i)
			{
				partition[j++] = dataLength & 0xFF;
				dataLength = dataLength >> 8;
			}
			
			tigerCompress(partition, res);
			var result:Array = [];
			for (i =0; i < res.length; ++i)
			{
				result.push(res[i].op0);
				result.push(res[i].op1);
			}
			return result; 
		}
		
		private static function tigerCompress(piece:Array, stateAr:Array):void
		{
			piece.position = 0;
			var a:Object = stateAr[0];
			var b:Object = stateAr[1];
			var c:Object = stateAr[2];
			
			var original:Array= [
				{op0:a.op0, op1:a.op1},
				{op0:b.op0, op1:b.op1},
				{op0:c.op0, op1:c.op1}
			];
			var xArray:Array = [];
			var oper0:uint;
			var oper1:uint;
			var i:int = 0;
			while(i<64)
			{
				oper0 = piece[i++] + (piece[i++]<<8) + (piece[i++]<<16) + (piece[i++]<<24);
				oper1 = piece[i++] + (piece[i++]<<8) + (piece[i++]<<16) + (piece[i++]<<24);
				xArray.push({op0:oper0, op1:oper1});
			}
			pass(a, b, c, xArray,5);
			keySchedule(xArray);
			pass(c, a, b, xArray,7);
			keySchedule(xArray);
			pass(b, c, a, xArray,9);
			
			xor64(stateAr[0],  original[0])
			sub64(stateAr[1], original[1]);
			add64(stateAr[2], original[2]);
		}
		
		private static function pass(a:Object, b:Object, c:Object, xArray:Array, mul:int):void
		{
			round(a, b, c, xArray[0], mul);
			round(b, c, a, xArray[1], mul);
			round(c, a, b, xArray[2], mul);
			round(a, b, c, xArray[3], mul);
			round(b, c, a, xArray[4], mul);
			round(c, a, b, xArray[5], mul);
			round(a, b, c, xArray[6], mul);
			round(b, c, a, xArray[7], mul);
		}
		
		private static function keySchedule(x:Array):void
		{
			sub64(x[0], {op0: x[7].op0^0xA5A5A5A5, op1:x[7].op1^0xA5A5A5A5});
			xor64(x[1], x[0]);
			add64(x[2], x[1]);
			sub64(x[3], {op0:x[2].op0^(uint(~x[1].op0)<<19), op1:~x[2].op1^(uint((x[1].op1)<<19)|(uint(x[1].op0)>>>13))});
			xor64(x[4], x[3]);
			add64(x[5], x[4]);
			sub64(x[6], {op0:~(x[5].op0)^((uint(x[4].op0)>>>23)|(uint(x[4].op1)<<9)), op1:(x[5].op1)^(uint(~(x[4].op1))>>>23)}); 
			xor64(x[7], x[6]); 
			add64(x[0], x[7]); 
			sub64(x[1], {op0:x[0].op0^(uint(~x[7].op0)<<19), op1:~(x[0].op1)^((uint(x[7].op1)<<19)|(uint(x[7].op0)>>>13))}); 
			xor64(x[2], x[1]); 
			add64(x[3], x[2]); 
			sub64(x[4], {op0:~(x[3].op0)^((uint(x[2].op0)>>>23)|(uint(x[2].op1)<<9)), op1:x[3].op1^(uint(~x[2].op1)>>>23)}); 
			xor64(x[5], x[4]); 
			add64(x[6], x[5]); 
			sub64(x[7], {op0:x[6].op0^0x89ABCDEF, op1:x[6].op1^0x01234567});
		}
		
		private static function round(a:Object, b:Object, c:Object, x:Object, mul:int):void
		{
			xor64(c, x);
			var c0:uint = c.op0;
			var c1:uint = c.op1;
			var temp0:uint = table1[((c0>>(0*8))&0xFF)<<1];
			var temp1:uint = table1[(((c0>>(0*8))&0xFF)<<1)+1];
			temp0 ^= table2[((c0>>(2*8))&0xFF)<<1];
			temp1 ^= table2[(((c0>>(2*8))&0xFF)<<1)+1];
			temp0 ^= table3[((c1>>(0*8))&0xFF)<<1];
			temp1 ^= table3[(((c1>>(0*8))&0xFF)<<1)+1];
			temp0 ^= table4[((c1>>(2*8))&0xFF)<<1];
			temp1 ^= table4[(((c1>>(2*8))&0xFF)<<1)+1];
			sub64(a, {op0:temp0, op1:temp1});
			temp0 = table4[((c0>>(1*8))&0xFF)<<1];
			temp1 = table4[(((c0>>(1*8))&0xFF)<<1)+1];
			temp0 ^= table3[(((c0>>(3*8))&0xFF)<<1)];
			temp1 ^= table3[(((c0>>(3*8))&0xFF)<<1)+1];
			temp0 ^= table2[((c1>>(1*8))&0xFF)<<1];
			temp1 ^= table2[(((c1>>(1*8))&0xFF)<<1)+1];
			temp0 ^= table1[(((c1>>(3*8))&0xFF)<<1)];
			temp1 ^= table1[(((c1>>(3*8))&0xFF)<<1)+1];
			add64(b, {op0:temp0, op1:temp1});
			switch(mul)
			{
				case 5: mul5(b);
					break;
				case 7: mul7(b);
					break;
				case 9: mul9(b);
					break;
				default:
					break;
			}
		}
		private static function xor64(s:Object, p:Object):void
		{
			s.op0 = uint(s.op0 ^ p.op0);
			s.op1 = uint(s.op1 ^ p.op1);
		}
		private static function sub64(s:Object, p:Object):void
		{
			var carry:uint = uint(uint(s.op0) < uint(p.op0));
			s.op0 = uint(uint(s.op0)- uint(p.op0));
			s.op1 = uint(s.op1- (uint(p.op1)+carry));
		}
		private static function add64(s:Object, p:Object):void
		{
			s.op0 = uint(s.op0+p.op0);
			var carry:uint = uint(uint(s.op0) < uint(p.op0)) ;
			s.op1 = uint(uint(s.op1)+uint(p.op1)+carry);
		}
		private static function mul5(s:Object):void
		{
			var tmpt0:uint = uint(s.op0 << 2);
			var tmpt1:uint = (uint(s.op1<<2))|(uint(s.op0>>>30));
			add64(s, {op0:tmpt0, op1:tmpt1});
		}
		private static function mul7(s:Object):void
		{
			var tmpt0:uint = uint(s.op0 << 3);
			var tmpt1:uint = uint(s.op1<<3)|uint(s.op0>>>29);
			var op:Object = {op0:tmpt0, op1:tmpt1};
			sub64(op, s);
			s.op0 = op.op0;
			s.op1 = op.op1;
		}
		private static function mul9(s:Object):void
		{
			var tmpt0:uint = uint(s.op0 << 3);
			var tmpt1:uint = uint(s.op1<<3)|uint(s.op0>>>29);
			add64(s, {op0:tmpt0, op1:tmpt1});
		}
	}
}